#include "Entry.hpp"

using namespace BFT::messages;

Entry::Entry(uint32_t _client, uint32_t _req, boost::shared_ptr<Digest> _digest)
{
	clientId = _client;
	requestId = _req;
	if (command.use_count()!=0 || bytes.use_count()!=0)
	{
		fprintf(stderr,"empty shared_ptr should have a 0 use_count...\n");
		exit(11);
	}
	digest=_digest;
	has_digest=true;
}

Entry::Entry(uint32_t _client, uint32_t _req, boost::shared_ptr<char> _command, uint32_t _command_length)
{
	clientId = _client;
	requestId = _req;
	command = _command;
	command_size = _command_length;
	has_digest = false;
	if (digest.use_count()!=0 || bytes.use_count()!=0)
	{
		fprintf(stderr,"empty shared_ptr should have a 0 use_count...\n");
		exit(11);
	}
}

boost::shared_ptr<Entry> Entry::fromBytes(boost::shared_ptr<char> _bytes, uint32_t _bytes_length, uint32_t _offset)
  {
	uint32_t byte_index = _offset; 
	
	if (_bytes_length < byte_index + COMMAND_DIGEST_FLAG_SIZE + CLIENT_ID_SIZE + REQUEST_ID_SIZE + COMMAND_SIZE_SIZE)
	{
		fprintf(stderr,"something wrong in creating an entry from bytes (1)\n");
		exit(12);
	}
	
	uint8_t digest_flag = *(uint8_t*)(_bytes.get()+byte_index); //setting the flag
	byte_index += COMMAND_DIGEST_FLAG_SIZE;
	bool has_digest = (digest_flag == 1);
	
	uint32_t clientId = ntohl(*(uint32_t*)(_bytes.get()+byte_index)); //setting clientID
	byte_index += CLIENT_ID_SIZE;
	
	uint32_t requestId = ntohl(*(uint32_t*)(_bytes.get()+byte_index)); //setting requestID
	byte_index += REQUEST_ID_SIZE;
	
	uint32_t command_size = ntohl(*(uint32_t*)(_bytes.get()+byte_index)); //setting command_size
	byte_index += COMMAND_SIZE_SIZE;
	
	if (_bytes_length < byte_index + command_size)
	{
		fprintf(stderr,"something wrong in creating an entry from bytes (2)\n");
		exit(13);
	}
	
	
	boost::shared_ptr<char> command( (char*)malloc(sizeof(char)*command_size), free);
	
	for (uint32_t i=0; i<command_size; i++)
		command.get()[i] = _bytes.get()[byte_index+i];  //setting the command
		
	boost::shared_ptr<Entry> entry;
		
	if (has_digest)
	{	
		boost::shared_ptr<Digest> digest (Digest::fromBytes(command, command_size));
		command.reset(); // ???
		entry.reset(new Entry(clientId, requestId, digest));
	} else
	{
		entry.reset(new Entry(clientId, requestId, command, command_size));
	}
	
	return entry;
  }


boost::shared_ptr< std::vector< boost::shared_ptr<Entry> > > Entry::getList(boost::shared_ptr<char> _buffer, uint32_t _buffer_length, uint32_t _e_count)
{
	//boost::shared_ptr< boost::shared_ptr<Entry> > entryList ((boost::shared_ptr<Entry>*)malloc( sizeof(boost::shared_ptr<Entry>)*_e_count ), free);
	
	boost::shared_ptr< std::vector<boost::shared_ptr<Entry> > > entryList (new std::vector<boost::shared_ptr<Entry> >());
		
	uint32_t count=0;
	uint32_t offset=0;
	
	while (offset < _buffer_length)
	{
		//entryList.get()[count] = Entry::fromBytes(_buffer, _buffer_length, offset);
		entryList.get()->push_back(Entry::fromBytes(_buffer, _buffer_length, offset));
		offset += entryList.get()->at(count).get()->getSize();
		count++;
	}
	
	if (count != _e_count)
	{
		fprintf(stderr,"ill formed entry list\n");
		exit(13);		
	}
	return entryList;
}

boost::shared_ptr<char> Entry::getBytes(int& _bytes_length)
{
	if (bytes.use_count() == 0)
	{
		uint32_t offset = 0;
		bytes.reset((char*)malloc(getSize()), free);
		if (has_digest)   // setting digest/command flag
			*((uint8_t*)bytes.get()) = (uint8_t)1;
		else
			*((uint8_t*)bytes.get()) = (uint8_t)0;
			
		offset += COMMAND_DIGEST_FLAG_SIZE;
		
		
		*((uint32_t*)(bytes.get()+offset)) = htonl(clientId); //setting clientId
		offset += CLIENT_ID_SIZE;
		
		*((uint32_t*)(bytes.get()+offset)) = htonl(requestId); //setting requestId
		offset += REQUEST_ID_SIZE;
		
		if (has_digest)
		{
			*((uint32_t*)(bytes.get()+offset)) = htonl((uint32_t)DIGEST_SIZE); //setting digest size
			offset += COMMAND_SIZE_SIZE;
			
			
			boost::shared_ptr<char> digest_bytes(digest.get()->getBytes());
			memcpy((void*)(bytes.get()+offset), (const void*)digest_bytes.get(), DIGEST_SIZE); // setting the digest
			offset += DIGEST_SIZE;
		}
		else
		{
			*((uint32_t*)(bytes.get()+offset)) = htonl(command_size); //setting command size
			offset += COMMAND_SIZE_SIZE;
			
			memcpy((void*)(bytes.get()+offset), (const void*)command.get(), command_size); //setting the command
			offset += command_size;			
		}
		
		if (offset != getSize())
		{
			fprintf(stderr,"something wrong in Entry::getBytes(). offset: %d, getSize(): %d\n",offset,getSize());
			exit(70);
		}
		
		_bytes_length = offset;	
	}
	else
	{
		_bytes_length = getSize();
	}
	
	//in any case
	return bytes;
}

boost::shared_ptr<Digest> Entry::getMyDigest()
{
	if (digest.use_count() != 0)
	{
		fprintf(stderr,"Should never get my digest if the digest already exists\n");
		exit(14);
	}
	
	if (my_digest.use_count()==0)
	{
		my_digest.reset(new Digest());
	}
	return my_digest;
}


bool Entry::matches(boost::shared_ptr<Digest> _digest)
{
	return _digest->equals(getMyDigest());
}

bool Entry::equals(boost::shared_ptr<Entry> _entry)
{
	if ( getClient()!=_entry.get()->getClient() || getRequestId()!=_entry.get()->getRequestId() || hasDigest()!=_entry.get()->hasDigest() )
		return false;
	if (has_digest)
	{
		return digest.get()->equals(_entry.get()->getDigest());
	} else
	{
		if (command_size != _entry.get()->getCommandSize())
			return false;
		bool different=false;	
		for (uint32_t index=0; index<command_size; index++)
		{	
			different = different || ( command.get()[index] != _entry.get()->getCommand().get()[index] );	
		}
		if (different)
			return false;
	}
	return true;
}

uint32_t Entry::computeSize(uint32_t _command_size)
{
	return REQUEST_ID_SIZE + COMMAND_SIZE_SIZE + _command_size + CLIENT_ID_SIZE + COMMAND_DIGEST_FLAG_SIZE;
}

void Entry::printInfo()
{
	fprintf(stdout,"Entry information: \n");
	fprintf(stdout,"Client id: %d\n", clientId);
	fprintf(stdout,"Request id: %d\n", requestId);
	fprintf(stdout,"Command size: %d\n",command_size);
	fprintf(stdout,"has digest: %d\n",has_digest);
}
